﻿using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using NLog;

namespace TfsGiggle.Core.Parsers
{
    /// <summary>
    /// Wraps ctags.exe
    /// </summary>
    class CTagsProcessWrapper : IDisposable
    {
        static LoggerWithEventId _logger = (LoggerWithEventId)LogManager.GetCurrentClassLogger(typeof(LoggerWithEventId));
        AutoResetEvent _outputWaitHandle;
        AutoResetEvent _errorWaitHandle;
        Process _process;
        StringBuilder _output;
        StringBuilder _error;

        void Dispose(bool isDisposing)
        {
            _logger.Trace("enter");
            if (isDisposing)
            {
                if (_outputWaitHandle != null)
                {
                    _outputWaitHandle.Dispose();
                    _outputWaitHandle = null;
                }
                if (_errorWaitHandle != null)
                {
                    _errorWaitHandle.Dispose();
                    _errorWaitHandle = null;
                }
                if (_process != null)
                {
                    if (!_process.HasExited)
                    {
                        _process.Kill();
                        _process.Dispose();
                        _process = null;
                    }
                }
            }
            _logger.Trace("exit");
        }

        private CTagsProcessWrapper() { }

        /// <summary>
        /// Parsed result
        /// </summary>
        public string Output { get { return _output.ToString(); } }

        /// <summary>
        /// Any errors encountered
        /// </summary>
        public string Error { get { return _error.ToString(); } }

        /// <summary>
        /// True if the ctags ended unexpectedly
        /// </summary>
        public bool HasExited { get { return _process.HasExited; } }

        /// <summary>
        /// Start ctags, stdoutput redirected to _output
        /// </summary>
        /// <param name="filePath"></param>
        public CTagsProcessWrapper(string filePath)
        {
            _logger.Trace("enter");

            string filterTerminator = "KillTheRiddick" + Environment.NewLine;
            var arguments = string.Format("--sort=no -x --filter=yes −−filter−terminator={0}", filterTerminator);
            _process = new Process();
            _process.StartInfo.FileName = filePath;
            _process.StartInfo.Arguments = arguments;
            _process.StartInfo.UseShellExecute = false;
            _process.StartInfo.RedirectStandardInput = true;
            _process.StartInfo.RedirectStandardOutput = true;
            _process.StartInfo.RedirectStandardError = true;
            _process.EnableRaisingEvents = true;
            _outputWaitHandle = new AutoResetEvent(false);
            _errorWaitHandle = new AutoResetEvent(false);
            _output = new StringBuilder();
            _error = new StringBuilder();

            _process.Exited += (sender, e) =>
            {
                _logger.Error("CTags process died unexpectedly; - exitcode:{0} processid {1}", _process.ExitCode, _process.Id);
            };

            _process.OutputDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    _outputWaitHandle.Set();
                }
                else
                {
                    if (e.Data.Contains(filterTerminator))
                    {
                        _outputWaitHandle.Set();
                    }
                    else
                    {
                        _output.AppendLine(e.Data);
                    }
                }
            };
            _process.ErrorDataReceived += (sender, e) =>
            {
                if (e.Data == null)
                {
                    _errorWaitHandle.Set();
                }
                else
                {
                    _error.AppendLine(e.Data);
                }
            };
            _process.Start();
            _process.BeginOutputReadLine();
            _process.BeginErrorReadLine();
            _logger.Trace("exit");
        }

        /// <summary>
        /// Parse content by creating a temp file, send it to CTags via StdInput and receive results via StdOutput.
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public string Parse(string fileName, string content)
        {
            _logger.Trace("enter '{0}' ctags process-id:{1}", fileName, _process.Id);

            if (_process.HasExited)
            {
                _logger.Error("Underlying ctags process {0} has exited", _process.Id);
                return null;
            }
            const int timeout = 2000;
            _output.Clear();
            _error.Clear();
            var tempFileName = string.Format("{0}", Path.Combine(Path.GetTempPath(), Thread.CurrentThread.ManagedThreadId.ToString() + Path.GetFileName(fileName)));
            File.WriteAllText(tempFileName, content);
            _process.StandardInput.WriteLine(tempFileName);
            _process.StandardInput.Flush();
            if (_outputWaitHandle.WaitOne(timeout) && _errorWaitHandle.WaitOne(timeout))
            {
                _logger.Warn("Timeout parsing '{0}'", fileName);
            }
            if (_process.HasExited)
            {
                _logger.Error("CTags death detected!", _process.Id);
            }
            if (File.Exists(tempFileName))
            {
                File.Delete(tempFileName);
            }
            _logger.Trace("exit");
            return _output.ToString();
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}